package edu.buffalo.cse.cse486_586.simpledht.service;

import java.util.Map;

import edu.buffalo.cse.cse486_586.simpledht.bean.DHTBean;
import edu.buffalo.cse.cse486_586.simpledht.bean.MessageBean;
import edu.buffalo.cse.cse486_586.simpledht.config.Configuration;
import edu.buffalo.cse.cse486_586.simpledht.config.MasterConfiguration;
import edu.buffalo.cse.cse486_586.simpledht.config.Node;
import edu.buffalo.cse.cse486_586.simpledht.entity.KeyValuePair;
import edu.buffalo.cse.cse486_586.simpledht.entity.MyPackage;
import edu.buffalo.cse.cse486_586.util.AppUtil;
import edu.buffalo.cse.cse486_586.util.CommonUtil;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.util.Log;

public class NetWorkService extends Service {
	// Debug params
	final private String TAG = "NetWorkService";
	// final private Boolean isDebug = true;
	// network listen thread
	Thread listenThread;
	// handler for communication between activity and service
	private Messenger outgoingMessager = null;
	private final Messenger incomingMessenger = new Messenger(
			new MessageHandler());

	// Target we publish for clients to send messages to IncomingHandler.
	@Override
	public IBinder onBind(Intent intent) {
		listenThread = new Thread(new ListeningThread(this));
		listenThread.start();

		return incomingMessenger.getBinder();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		this.listenThread.stop();
		// nm.cancel(R.string.service_started); // Cancel the persistent
		// notification.
	}

	public void initConfig(int localPort) {
		Configuration config = Configuration.getInstance();
		config.setSytemStatus(AppUtil.STATUS_INIT);
		if (localPort == AppUtil.MASTER_CONSOLE_PORT) {
			// initialize the master configuration
			MasterConfiguration.getInstance();
		}
		MessageBean mb = new MessageBean();
		MyPackage pkg = mb.generateJoinPkg(localPort);
		// send message to dev:5554
		Thread t = new Thread(new SendingThread(AppUtil.EMULATOR_IP,
				AppUtil.MASTER_REDIRCT_PORT, pkg.toString()));
		t.start();
	}

	public void processReceivedContent(String str) {
		String tokens[] = str.split(AppUtil.DELIMITER);
		// check whether the first token is number
		if (!CommonUtil.isNumeric(tokens[0]))
			return;
		int type = Integer.parseInt(tokens[0]);
		if (type >= AppUtil.PACKAGE_JOIN && type <= AppUtil.PACKAGE_STABLE) {
			processConfigPkg(str);
		} else {
			processDHTPkg(str);
		}

	}

	public void processDHTPkg(String str) {
		String tokens[] = str.split(AppUtil.DELIMITER);

		if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_INSERT
				&& tokens.length == 3) {
			insertKv(tokens[1], tokens[2]);
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_LOOK_UP
				&& tokens.length == 3) {
			remoteLookUp(tokens[1], tokens[2]);
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_KEY_VALUE
				&& tokens.length == 2) {
			updateUI(tokens[1]);
		}

	}

	public void processConfigPkg(String str) {
		MessageBean mb = new MessageBean();
		String tokens[] = str.split(AppUtil.DELIMITER);
		if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_JOIN
				&& tokens.length == 2) {
			MasterConfiguration masterConfig = MasterConfiguration
					.getInstance();
			Configuration config = Configuration.getInstance();
			if (config.getSytemStatus() == AppUtil.STATUS_STABLE) {
				// send reset pkg to all alive node except the new coming node
				MyPackage pkg = mb.generateResetPkg();
				for (Map.Entry<String, Node> entry : masterConfig.getNodeList()
						.entrySet()) {
					Thread t = new Thread(new SendingThread(
							AppUtil.EMULATOR_IP, entry.getValue()
									.getRedirPort(), pkg.toString()));
					t.start();
				}
				masterConfig.getReadyPkgContainer().clear();
			}
			// new joining node, predecessor and successor are empty
			Node n = mb.processJoinMessage(tokens[1]);
			if (mb.addNodeToList(n)) {
				// receive all JOIN_MESSAGE from Node
				mb.sendSetUpMessage();
			}
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_RESET) {
			Configuration config = Configuration.getInstance();
			config.setSytemStatus(AppUtil.STATUS_INIT);
			// disable all button on UI
			try {
				Message msg = Message.obtain(null, AppUtil.MSG_DISABLE_BUTTON);
				outgoingMessager.send(msg);
			} catch (RemoteException e) {
				Log.e(TAG, "Exception", e);
			}
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_SET_UP
				&& tokens.length == 4) {
			// process package and set up configuration singleton
			mb.processSetUpMessage(str);
			// key-value pair transfer
			transferedKv();
			Configuration config = Configuration.getInstance();
			config.setSytemStatus(AppUtil.STATUS_READY);
			// send ready pkg to master
			MyPackage pkg = mb.generateReadyPkg();
			Thread t = new Thread(new SendingThread(AppUtil.EMULATOR_IP,
					AppUtil.MASTER_REDIRCT_PORT, pkg.toString()));
			t.start();
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_READY
				&& tokens.length == 2) {
			MasterConfiguration masterConfig = MasterConfiguration
					.getInstance();
			masterConfig.addreadyPkg(tokens[1], str);

			// send stable pkg to all node
			if (masterConfig.getReadyPkgContainer().size() >= AppUtil.numDev) {
				MyPackage pkg = mb.generateStatablePkg();
				for (Map.Entry<String, Node> entry : masterConfig.getNodeList()
						.entrySet()) {
					Thread t = new Thread(new SendingThread(
							AppUtil.EMULATOR_IP, entry.getValue()
									.getRedirPort(), pkg.toString()));
					t.start();
				}
			}
		} else if (Integer.parseInt(tokens[0]) == AppUtil.PACKAGE_STABLE) {
			Configuration config = Configuration.getInstance();
			config.setSytemStatus(AppUtil.STATUS_STABLE);
			try {
				Message msg = Message.obtain(null, AppUtil.MSG_ENABLE_BUTTON);
				outgoingMessager.send(msg);
			} catch (RemoteException e) {
				Log.e(TAG, "Exception", e);
			}
		}
	}

	public void insertKv(String key, String value) {
		DHTBean dht = new DHTBean();
		if (dht.isInrange(String.valueOf(key))) {
			ContentValues cv = new ContentValues();
			cv.put(AppUtil.KeyValueTable.COL_PROVIDER_KEY, key);
			cv.put(AppUtil.KeyValueTable.COL_PROVIDER_VALUE, value);
			getContentResolver().insert(AppUtil.CONTENT_URI, cv);
		} else {
			dht.passKeyValePair(new KeyValuePair(key, value));
		}

		try {
			Message msg = Message.obtain(null, AppUtil.MSG_UPDATE_VIEWLIST);
			outgoingMessager.send(msg);
		} catch (RemoteException e) {
			Log.d(TAG, "Exception", e);
		}
	}

	public void retrieveKV(String key) {
		DHTBean dht = new DHTBean();

		if (dht.isInrange(key)) {
			String rs = searchFromCP(key);
			if (rs.length() <= 0)
				rs = "can't fount in content provider";

			updateUI(rs);
		} else {
			MessageBean mb = new MessageBean();
			MyPackage pkg = mb.generateLookUpPkg(key);

			Node n = Configuration.getInstance().getNode();
			Thread t = new Thread(new SendingThread(AppUtil.EMULATOR_IP,
					CommonUtil.getRedirectPort(n.getSuccessor()),
					pkg.toString()));
			t.start();
		}
	}

	public void remoteLookUp(String senderPort, String key) {
		DHTBean dht = new DHTBean();
		if (dht.isInrange(key)) {
			String rs = searchFromCP(key);
			if (rs.length() <= 0)
				rs = "can't fount in content provider";

			Thread t = new Thread(new SendingThread(AppUtil.EMULATOR_IP,
					CommonUtil.getRedirectPort(Integer.valueOf(senderPort)),
					AppUtil.PACKAGE_KEY_VALUE + AppUtil.DELIMITER + rs));
			t.start();
		} else {
			MessageBean mb = new MessageBean();
			MyPackage pkg = mb.generateLookUpPkg(key);

			Node n = Configuration.getInstance().getNode();
			Thread t = new Thread(new SendingThread(AppUtil.EMULATOR_IP,
					CommonUtil.getRedirectPort(n.getSuccessor()),
					pkg.toString()));
			t.start();
		}
	}

	public String searchFromCP(String key) {
		Cursor c = getContentResolver().query(
				AppUtil.CONTENT_URI,
				new String[] { AppUtil.KeyValueTable.COL_ID,
						AppUtil.KeyValueTable.COL_PROVIDER_KEY,
						AppUtil.KeyValueTable.COL_PROVIDER_VALUE },
				AppUtil.KeyValueTable.COL_PROVIDER_KEY + " ='" + key + "'",
				null, null);

		String out = "";
		if (c.moveToFirst()) {
			while (!c.isAfterLast()) {
				out += c.getString(c
						.getColumnIndexOrThrow(AppUtil.KeyValueTable.COL_PROVIDER_KEY));
				out += " : ";
				out += c.getString(c
						.getColumnIndexOrThrow(AppUtil.KeyValueTable.COL_PROVIDER_VALUE));
				c.moveToNext();
			}
		}

		return out;
	}

	public void transferedKv() {
		Cursor c = getContentResolver().query(
				AppUtil.CONTENT_URI,
				new String[] { AppUtil.KeyValueTable.COL_ID,
						AppUtil.KeyValueTable.COL_PROVIDER_KEY,
						AppUtil.KeyValueTable.COL_PROVIDER_VALUE }, null, null,
				null);

		DHTBean dht = new DHTBean();
		if (c.moveToFirst()) {
			while (!c.isAfterLast()) {
				String key = c
						.getString(c
								.getColumnIndexOrThrow(AppUtil.KeyValueTable.COL_PROVIDER_KEY));
				String value = c
						.getString(c
								.getColumnIndexOrThrow(AppUtil.KeyValueTable.COL_PROVIDER_VALUE));

				if (!dht.isInrange(key)) {
					// remove
					getContentResolver().delete(
							AppUtil.CONTENT_URI,
							AppUtil.KeyValueTable.COL_PROVIDER_KEY + "= '"
									+ key + "'", null);
					// send it out
					dht.passKeyValePair(new KeyValuePair(key, value));
				}
				c.moveToNext();
			}
		}
	}

	public void updateUI(String rs) {
		try {
			Message msg = Message.obtain(null, AppUtil.MSG_RETREIVE_RESULT);
			android.os.Bundle b = new android.os.Bundle();
			b.putString(AppUtil.RESULT, rs);
			msg.setData(b);
			outgoingMessager.send(msg);
		} catch (RemoteException e) {
			Log.e(TAG, "Exception", e);
		}
	}

	// Handler of messages from binded activity.
	class MessageHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case AppUtil.MSG_REGISTER_CLIENT:
				outgoingMessager = msg.replyTo;
				break;
			case AppUtil.MSG_TEST_CASE_1:
				Thread t1 = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							for (int i = 0; i < 10; i++) {
								Thread.sleep(1000);
								insertKv(String.valueOf(i), "TEST" + i);
							}
						} catch (InterruptedException e) {
							Log.e(TAG, "Exception", e);
						}
					}
				});
				t1.start();
				break;
			case AppUtil.MSG_ADD:
				String insertedKey = msg.getData().getString(AppUtil.KEY);
				String value = msg.getData().getString(AppUtil.VALUE);
				insertKv(insertedKey, value);
				break;

			case AppUtil.MSG_RETREIVE:
				String key = msg.getData().getString(AppUtil.KEY);
				retrieveKV(key);
				break;
			case AppUtil.MSG_JOIN:
				// Initiate local port and logic vector
				TelephonyManager tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
				String portStr = tel.getLine1Number().substring(
						tel.getLine1Number().length() - 4);

				initConfig(Integer.parseInt(portStr));
				break;
			default:
				super.handleMessage(msg);
			}
		}
	}
}
